using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using CkSoftware.GroupMe.Sdk.Core.Dao;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CkSoftware.GroupMe.Sdk.Core.Serialization
{
	/// <summary>
	/// Custom Json converter for Emoji Definitions.
	/// </summary>
	public class GroupMeEmojiMapConverter : JsonConverter
	{
		/// <summary>
		/// Writes the JSON representation of the object.
		/// </summary>
		/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
		/// <param name="value">The value.</param>
		/// <param name="serializer">The calling serializer.</param>
		public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
		{
			if (value is IEnumerable && value is IEnumerable<GroupMeEmojiDefinition>)
			{
				var emojiDefinitions = (IEnumerable<GroupMeEmojiDefinition>) value;

				writer.WriteStartArray();

				foreach (GroupMeEmojiDefinition definition in emojiDefinitions)
				{
					ProcessObjectToJson(definition, writer);
				}

				writer.WriteEndArray();
			}
			else if (value is GroupMeEmojiDefinition)
			{
				ProcessObjectToJson((GroupMeEmojiDefinition) value, writer);
			}
			else
			{
				throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
					"The type '{0}' cannot be serialized as emoji.", value.GetType().FullName));
			}
		}

		/// <summary>
		/// Reads the JSON representation of the object.
		/// </summary>
		/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
		/// <param name="objectType">Type of the object.</param>
		/// <param name="existingValue">The existing value of object being read.</param>
		/// <param name="serializer">The calling serializer.</param>
		/// <returns>
		/// The object value.
		/// </returns>
		public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
		{
			if (reader.TokenType == JsonToken.StartArray)
			{
				JArray dataArray = JArray.Load(reader);
				return dataArray
					.Select(item => ProcessJsonToObject(item, serializer))
					.Where(processedData => processedData != null)
					.ToList();
			}

			JObject data = JObject.Load(reader);
			return ProcessJsonToObject(data, serializer);
		}

		private void ProcessObjectToJson(GroupMeEmojiDefinition emoji, JsonWriter writer)
		{
			writer.WriteStartArray();

			writer.WriteValue(emoji.PackId);
			writer.WriteValue(emoji.EmojiId);

			writer.WriteEndArray();
		}

		private GroupMeEmojiDefinition ProcessJsonToObject(JToken item, JsonSerializer serializer)
		{
			if (item.Type != JTokenType.Array)
			{
				throw new ArgumentException("The item must be a Json array to successfully deserialize into a emoji mapping.",
					"item");
			}

			var emojiData = serializer.Deserialize<int[]>(item.CreateReader());

			if (emojiData == null || emojiData.Length != 2)
			{
				throw new FormatException(string.Format(CultureInfo.CurrentCulture,
					"The item must be a Json array with exactly 2 entries ([0] = pack id, [1] = emoji id). The data was '{0}'", item));
			}

			return new GroupMeEmojiDefinition {PackId = emojiData[0], EmojiId = emojiData[1]};
		}

		/// <summary>
		/// Determines whether this instance can convert the specified object type.
		/// </summary>
		/// <param name="objectType">Type of the object.</param>
		/// <returns>
		/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
		/// </returns>
		public override bool CanConvert(Type objectType)
		{
			return objectType == typeof (GroupMeEmojiDefinition) || objectType == typeof (IEnumerable<GroupMeEmojiDefinition>);
		}
	}
}